#ifndef __LOGLOG_H__
#define __LOGLOG_H__
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#undef LOGERR
#undef LLOG
#undef LOGFEW

#if defined(linux) || defined(__APPLE__)
#define my_localtime(a,b) localtime_r(a,b)
#else
#define my_localtime(a,b) localtime_s(b,a)
#endif

extern struct log_handle *log_handle;
extern int log_with_ts;
extern int log_with_func;
extern int log_with_line;
extern int log_with_file;
extern int verbose;
extern int log_file_info_width;
extern int log_func_info_width;

enum LOGLEVEL { LOGLEVEL_0, LOGLEVEL_1, LOGLEVEL_2 };
struct log_handle {
    FILE *fp;
    FILE *old_fp;
    pthread_mutex_t lock;
    int enable_dump;
    enum LOGLEVEL level;
    char path[512], path2[512], path3[512];
};


static inline void get_day_start_end_ts(time_t ts, time_t *start, time_t *end)
{
    struct tm tm; my_localtime(&ts, &tm);
    if (start) { tm.tm_hour = 0; tm.tm_min  = 0; tm.tm_sec  = 0; *start = mktime(&tm); }
    if (end  ) { tm.tm_hour = 23;tm.tm_min  = 59;tm.tm_sec  = 59;*end   = mktime(&tm); }
}

static inline void get_day_start_end_str(time_t ts, char *start, char *end)
{
    struct tm tm; my_localtime(&ts, &tm);
    sprintf(start, "%u-%02u-%02u 00:00:00", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
    sprintf(end  , "%u-%02u-%02u 23:59:59", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
}

static inline char *period2human(uint64_t sec, char* buf)
{
	uint64_t _sec = sec;
    uint64_t day = _sec / 60 / 60 / 24; _sec -= day * 60 * 60 * 24;
    uint64_t hour= _sec / 60 / 60     ; _sec -= hour* 60 * 60     ;
    uint64_t min = _sec / 60          ; _sec -= min * 60          ;
    if (day ) { sprintf(buf, "%"PRIu64"d%02"PRIu64"h%02"PRIu64"m%02"PRIu64"s", day, hour, min, _sec); return buf; }
    if (hour) { sprintf(buf, "%"PRIu64"h%02"PRIu64"m%02"PRIu64"s"      , hour, min, _sec     ); return buf; }
    if (min ) { sprintf(buf, "%"PRIu64"m%02"PRIu64"s"            , min, _sec           ); return buf; }
    ;         { sprintf(buf, "%"PRIu64"s"                  , _sec                ); return buf; }
}

static inline char *period2human_2field(uint64_t sec, char* buf, int left_align, int html_format)
{
    const char *div = html_format ? "&nbsp;" : " ";
    uint64_t day = sec / 60 / 60 / 24; sec -= day * 60 * 60 * 24;
    uint64_t hour= sec / 60 / 60     ; sec -= hour* 60 * 60     ;
    uint64_t min = sec / 60          ; sec -= min * 60          ;
    if (day) {
        if (day >= 10) { sprintf(buf, "%"PRIu64"d%02"PRIu64"h", day, hour); return buf; }
        if (left_align) sprintf(buf, "%"PRIu64"d%02"PRIu64"h%s", day, hour, div);
        else            sprintf(buf, "%s%"PRIu64"d%02"PRIu64"h", div, day, hour);
    } else if (hour) {
        if (hour >= 10) { sprintf(buf, "%"PRIu64"h%02"PRIu64"m", hour, min); return buf; }
        if (left_align) sprintf(buf, "%"PRIu64"h%02"PRIu64"m%s", hour, min, div);
        else            sprintf(buf, "%s%"PRIu64"h%02"PRIu64"m", div, hour, min);
    } else if (min) {
        if (min >= 10) { sprintf(buf, "%"PRIu64"m%02"PRIu64"s", min, sec ); return buf; }
        if (left_align) sprintf(buf, "%"PRIu64"m%02"PRIu64"s%s", min, sec, div);
        else            sprintf(buf, "%s%"PRIu64"m%02"PRIu64"s", div, min, sec);
    } else if (sec >= 10) {
        if (left_align) sprintf(buf, "%"PRIu64"s%s%s%s", sec, div, div, div);
        else            sprintf(buf, "%s%s%s%"PRIu64"s", div, div, div, sec);
    } else {
        if (left_align) sprintf(buf, "%"PRIu64"s%s%s%s", sec, div, div, div);
        else            sprintf(buf, "%s%s%s%"PRIu64"s", div, div, div, sec);
    }
    return buf;
}

static inline char *period2human_1field(uint64_t sec, char* buf, int left_align, int html_format)
{
    const char *div = html_format ? "&nbsp;" : " ";
    uint64_t day = sec / 60 / 60 / 24; sec -= day * 60 * 60 * 24;
    uint64_t hour= sec / 60 / 60     ; sec -= hour* 60 * 60     ;
    uint64_t min = sec / 60          ; sec -= min * 60          ;
    if (day) {
        if (day >= 10) { sprintf(buf, "%"PRIu64"d", day); return buf; }
        if (left_align) sprintf(buf, "%"PRIu64"d%s", day, div);
        else            sprintf(buf, "%s%"PRIu64"d", div, day);
    } else if (hour) {
        if (hour >= 10) { sprintf(buf, "%"PRIu64"h", hour); return buf; }
        if (left_align) sprintf(buf, "%"PRIu64"h%s", hour, div);
        else            sprintf(buf, "%s%"PRIu64"h", div, hour);
    } else if (min) {
        if (min >= 10) { sprintf(buf, "%"PRIu64"m", min); return buf; }
        if (left_align) sprintf(buf, "%"PRIu64"m%s", min, div);
        else            sprintf(buf, "%s%"PRIu64"m", div, min);
    } else {
        if (sec >= 10) { sprintf(buf, "%"PRIu64"s", sec); return buf; }
        if (left_align) sprintf(buf, "%"PRIu64"s%s", sec, div);
        else            sprintf(buf, "%s%"PRIu64"s", div, sec);
    }
    return buf;
}

//div1 null, without date
//div3 null, without timezone
static inline char *ts2str(time_t ts, char *buf, const char *div1, const char *div2, const char *div3, int with_time_zone, char *tailer)
{
    static int time_zone = 100;
    if (with_time_zone && time_zone == 100) {
        time_t time_utc = 0;
        struct tm *p_tm_time = localtime( &time_utc );
        time_zone = (p_tm_time->tm_hour > 12) ? (p_tm_time->tm_hour - 24) : p_tm_time->tm_hour;
    }
    struct tm tm; my_localtime(&ts, &tm);
    if (with_time_zone) {
        if (div1) {
            if (time_zone >= 0)
                sprintf(buf, "%u%s%02u%s%02u%s%02u%s%02u%s%02u+%02u:00%s", tm.tm_year + 1900, div1, tm.tm_mon + 1, div1, tm.tm_mday, div2, tm.tm_hour, div3, tm.tm_min, div3, tm.tm_sec, time_zone, tailer);
            else
                sprintf(buf, "%u%s%02u%s%02u%s%02u%s%02u%s%02u%02d:00%s", tm.tm_year + 1900, div1, tm.tm_mon + 1, div1, tm.tm_mday, div2, tm.tm_hour, div3, tm.tm_min, div3, tm.tm_sec, time_zone, tailer);
        } else {
            if (time_zone >= 0)
                sprintf(buf, "%02u%s%02u%s%02u+%02u:00%s", tm.tm_hour, div3, tm.tm_min, div3, tm.tm_sec, time_zone, tailer);
            else
                sprintf(buf, "%02u%s%02u%s%02u%02d:00%s" , tm.tm_hour, div3, tm.tm_min, div3, tm.tm_sec, time_zone, tailer);
        }
    } else {
        if (div1) {
            if (time_zone >= 0)
                sprintf(buf, "%u%s%02u%s%02u%s%02u%s%02u%s%02u%s", tm.tm_year + 1900, div1, tm.tm_mon + 1, div1, tm.tm_mday, div2, tm.tm_hour, div3, tm.tm_min, div3, tm.tm_sec, tailer);
            else
                sprintf(buf, "%u%s%02u%s%02u%s%02u%s%02u%s%02u%s", tm.tm_year + 1900, div1, tm.tm_mon + 1, div1, tm.tm_mday, div2, tm.tm_hour, div3, tm.tm_min, div3, tm.tm_sec, tailer);
        } else {
            if (time_zone >= 0)
                sprintf(buf, "%02u%s%02u%s%02u%s", tm.tm_hour, div3, tm.tm_min, div3, tm.tm_sec, tailer);
            else
                sprintf(buf, "%02u%s%02u%s%02u%s", tm.tm_hour, div3, tm.tm_min, div3, tm.tm_sec, tailer);
        }
    }
    return buf;
}

static inline char *ts2str_date(time_t ts, char *buf) { struct tm tm; my_localtime(&ts, &tm); sprintf(buf, "%u-%02u-%02u", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); return buf; }
static inline char *ts2str_short_pure(time_t ts, char *buf) {
    time_t now = time(NULL); struct tm tm, tm_now; my_localtime(&ts, &tm); my_localtime(&now, &tm_now);
    if (tm.tm_year != tm_now.tm_year) { sprintf(buf, "%u/%02u/%02u %02u:%02u:%02u", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); return buf; }
    if (tm.tm_mon  != tm_now.tm_mon ) { sprintf(buf, "%02u/%02u %02u:%02u:%02u", tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); return buf; }
    if (tm.tm_mday != tm_now.tm_mday) { sprintf(buf, "%02u/%02u %02u:%02u:%02u", tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); return buf; }
    sprintf(buf, "%02u:%02u:%02u", tm.tm_hour, tm.tm_min, tm.tm_sec); return buf;
}
static inline char *ts2str_short_simple(time_t ts, char *buf) {
    time_t now = time(NULL); struct tm tm, tm_now; my_localtime(&ts, &tm); my_localtime(&now, &tm_now);
    if (tm.tm_mday != tm_now.tm_mday) { sprintf(buf, "%02u/%02u/%02u", (tm.tm_year + 1900) % 100, tm.tm_mon + 1, tm.tm_mday); return buf; }
    sprintf(buf, "%02u:%02u:%02u", tm.tm_hour, tm.tm_min, tm.tm_sec); return buf;
}

static inline char *ts2str_short(time_t ts, char *buf) {
    time_t now = time(NULL); struct tm tm, tm_now; my_localtime(&ts, &tm); my_localtime(&now, &tm_now);
    if (tm.tm_year != tm_now.tm_year) { sprintf(buf, "&nbsp;%u/%02u", tm.tm_year + 1900, tm.tm_mon + 1); return buf; }
    if (tm.tm_mon  != tm_now.tm_mon) { sprintf(buf, "&nbsp;&nbsp;&nbsp;%02u/%02u", tm.tm_mon + 1, tm.tm_mday); return buf; }
    if (tm.tm_mday != tm_now.tm_mday) { sprintf(buf, "%02u %02u:%02u", tm.tm_mday, tm.tm_hour, tm.tm_min); return buf; }
    sprintf(buf, "%02u:%02u:%02u", tm.tm_hour, tm.tm_min, tm.tm_sec); return buf;
}

#define LLOG(fmt, ...)  do { \
    char timestr[128] = ""; if (log_with_ts)   { ts2str(time(NULL), timestr, "-", "T", ":", 1, " ");          } timestr[sizeof(timestr) - 1] = 0; \
    char filestr[128] = ""; if (log_with_file) { snprintf(filestr, sizeof(filestr), " %-*s ", log_file_info_width, __FILE__);     } filestr[sizeof(filestr) - 1] = 0; \
    char funcstr[128] = ""; if (log_with_func) { snprintf(funcstr, sizeof(funcstr), " %-*s ", log_func_info_width, __func__);     } funcstr[sizeof(funcstr) - 1] = 0; \
    char linestr[128] = ""; if (log_with_line) { snprintf(linestr, sizeof(linestr), "line %-4d ", __LINE__);  } linestr[sizeof(linestr) - 1] = 0; \
    printf("%s%s%s%s"fmt, timestr, filestr, funcstr, linestr, ##__VA_ARGS__); fflush(stdout); \
    if (log_handle && log_handle->path[0]) { \
        if (log_handle->fp == 0) { \
            pthread_mutex_lock(&log_handle->lock); \
            if (log_handle->fp == 0) { FILE *fp = fopen(log_handle->path, "a"); log_handle->fp = fp; } \
            pthread_mutex_unlock(&log_handle->lock); \
        } \
        FILE *fp = log_handle->fp; \
        if (fp == NULL) { \
            static uint64_t last_time = 0;              \
            if (time(NULL) - last_time >= 10) { last_time = time(NULL) - 1; printf("%s %s %d fail to fopen %s[%m]\n", __FILE__, log_with_func ? __func__ : "", __LINE__, log_handle->path); fflush(stdout); } \
        } \
        if (fp) { \
            fprintf(fp, "%s %s %s %s "fmt, timestr, filestr, funcstr, linestr, ##__VA_ARGS__); fflush(fp); \
            if (ftell(fp) > 1*1024*1024) { \
                pthread_mutex_lock(&log_handle->lock); \
                if (ftell(fp) > 1*1024*1024) { \
                    rename(log_handle->path2, log_handle->path3); rename(log_handle->path, log_handle->path2); \
                    FILE *old_fp = log_handle->old_fp; \
                    if (old_fp) fclose(old_fp); \
                    log_handle->old_fp = fp; \
                    log_handle->fp = 0; \
                } \
                pthread_mutex_unlock(&log_handle->lock); \
            } \
        }\
    } \
} while(0)

#define LOGMSG(fmt, ...)                do { if (verbose >= LOGLEVEL_2 || (log_handle && log_handle->level >= LOGLEVEL_2)) LLOG(fmt, ##__VA_ARGS__); } while(0)
#define LOGERR(fmt, ...)                do { if (verbose >= LOGLEVEL_1 || (log_handle && log_handle->level >= LOGLEVEL_1)) LLOG(fmt, ##__VA_ARGS__); } while(0)
#define LOGFEW(fmt, ...)                do { if (verbose >= LOGLEVEL_1 || (log_handle && log_handle->level >= LOGLEVEL_1)) { static uint64_t last_time = 0; if (time(NULL) - last_time >= 10) { last_time = time(NULL); LLOG(fmt, ##__VA_ARGS__); } } } while(0)
#define LOGERR_R(fmt, ...)              do { if (verbose >= LOGLEVEL_1 || (log_handle && log_handle->level >= LOGLEVEL_1)) LLOG("\033[31m"fmt"\033[0m", ##__VA_ARGS__); } while(0)
#define LOGINF(fmt, ...)                do { if (verbose >= LOGLEVEL_1 || (log_handle && log_handle->level >= LOGLEVEL_1)) LLOG(fmt, ##__VA_ARGS__); } while(0)
#define LOGDIE(fmt, ...)                do { if (verbose >= LOGLEVEL_1 || (log_handle && log_handle->level >= LOGLEVEL_1)) LLOG(fmt, ##__VA_ARGS__); exit(-1); } while(0)
#define DUMPMSG(data, len, fmt, ...)    do { if (log_handle && log_handle->level >= LOGLEVEL_2 && log_handle->enable_dump) { int _len = len; int _i; char *_p = (char *)data; LLOG(fmt, ##__VA_ARGS__); printf("DUMP %4u byte:", _len); for (_i=0;_i<(int)_len;_i++) { if (isprint(_p[_i]))printf(" %c",_p[_i]); else printf("%02X",(unsigned char)_p[_i]); } printf("\n"); fflush(stdout); } } while(0)
#define DUMPHEXMSG(data, len, fmt, ...) do { if (log_handle && log_handle->level >= LOGLEVEL_2 && log_handle->enable_dump) { int _len = len; int _i; char *_p = (char *)data; LLOG(fmt, ##__VA_ARGS__); printf("DUMP %4u byte:", _len);  for (_i=0;_i<(int)_len;_i++) { printf("%02x",(unsigned char)_p[_i]); } printf("\n"); fflush(stdout); } } while(0)
#define DUMPFEW(data, len, fmt, ...)    do { if (log_handle && log_handle->level >= LOGLEVEL_1 && log_handle->enable_dump) { static uint64_t last_time = 0; if (time(NULL) - last_time >= 10) { last_time = time(NULL); int _len = len; int _i; char *_p = (char *)data; LLOG(fmt, ##__VA_ARGS__); printf("DUMP %4u byte:", _len); for (_i=0;_i<(int)_len;_i++) { if (isprint(_p[_i]))printf(" %c",_p[_i]); else printf("%02X",(unsigned char)_p[_i]); } printf("\n"); fflush(stdout); } } } while(0)
#define DUMP(data, len, fmt, ...)       do { int _len = len; int _i; char *_p = (char *)data; LLOG(fmt, ##__VA_ARGS__); printf("DUMP %4u byte:", _len); for (_i=0;_i<(int)_len;_i++) { if (isprint(_p[_i]))printf(" %c",_p[_i]); else printf("%02X",(unsigned char)_p[_i]); } printf("\n"); fflush(stdout); } while(0)
#define DUMPHEX(data, len, fmt, ...)    do { int _len = len; int _i; char *_p = (char *)data; LLOG(fmt, ##__VA_ARGS__); printf("DUMP %4u byte:", _len); for (_i=0;_i<(int)_len;_i++) { printf("%02x",(unsigned char)_p[_i]); } printf("\n"); fflush(stdout); } while(0)

static inline int log_init2(enum LOGLEVEL level, int enable_dump, const char *path, const char *path2, const char *path3)
#define           log_init2(v,e,p1,p2,p3) ({ int _ret = 0; if (log_handle) { LOGERR("log_handle already valid, should not call me again\n"); } else { _ret = log_init2(v,e,p1,p2,p3); } _ret; })
{
    if (log_handle) { LOGERR("log_handle already valid, should not call me again\n"); abort(); }
    struct log_handle *handle = calloc(1, sizeof(*handle)); if (handle == NULL) { LOGERR("fail to calloc\n"); return -1; }
    handle->level = level;
    handle->enable_dump = enable_dump;
    strcpy(handle->path, path);
    strcpy(handle->path2, path2);
    strcpy(handle->path3, path3);
    pthread_mutex_init(&handle->lock, NULL);
    log_handle = handle;
    return 0;
}

static inline time_t datetime2ts(char *src)
{
    struct tm tm; memset(&tm, 0, sizeof(tm));
    uint32_t y,m,d,h,f,s;
    sscanf(src, "%u-%02u-%02u %02u:%02u:%02u", &y,&m,&d,&h,&f,&s);
    tm.tm_year = y - 1900;
    tm.tm_mon  = m - 1;
    tm.tm_mday = d;
    tm.tm_hour = h;
    tm.tm_min  = f;
    tm.tm_sec  = s;
    return mktime(&tm);
}

#endif
